package com.levi.http.base;

import android.content.Context;
import android.os.Build;
import android.util.Log;
import android.util.Pair;

import com.levi.http.HttpClientManager;
import com.levi.http.R;
import com.levi.http.inter.IErrorParser;
import com.levi.http.inter.IHttpTask;
import com.levi.http.inter.IRequestDecorator;
import com.levi.http.inter.IRespParser;
import com.levi.http.inter.IRespProcessor;
import com.levi.http.inter.IResult;
import com.levi.http.type.Method;
import com.levi.http.utils.FileMediaTypeParser;
import com.levi.utils.UiHandler;
import com.levi.utils.encrypt.MD5;
import com.levi.utils.encrypt.Sha1Util;
import com.levi.utils.log.LogUtils;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.annotation.StringRes;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.FormBody;
import okhttp3.Headers;
import okhttp3.HttpUrl;
import okhttp3.MultipartBody;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;

/**
 * Created by Levi on 2018/3/13.
 */

public abstract class HttpRequestTask<T, E> implements IHttpTask {
    public static final String CATCH_ERROR_CODE = "9527";
    public static final String ERROR_CODE_EXCEPTION = "EXCEPTION";

    private static final String TAG = "request";
    private static final String AMPERSAND = "&";
    private static final String EQUAL = "=";
    private static final String QUESTION = "?";

    private Context mContext;
    private final String mTaskId;
    private String mGroupTag;
    private HttpCallback<T, E> mCallback;
    private Call mHttpCall;
    private IRequestDecorator mRequestDecorator;

    public HttpRequestTask(@NonNull Context context, HttpCallback<T, E> callback) {
        this.mTaskId = UUID.randomUUID().toString();
        this.mContext = context;
        this.mCallback = callback;
    }

    /**
     * 请求结果类型
     *
     * @return
     */
    public abstract Type getRespType();

    /**
     * extra 结果类型
     *
     * @return
     */
    public abstract Type getExtraType();

    /**
     * 获取请求结果脱壳器
     *
     * @return
     */
    public abstract IRespProcessor getRespProcessor();

    /**
     * 获取请求错误转换器
     *
     * @return
     */
    public abstract IErrorParser getErrorParser();

    /**
     * 获取请求结果转换器
     *
     * @return
     */
    public abstract IRespParser<T> getRespParser();

    /**
     * 获取结果extra转换器
     *
     * @return
     */
    public abstract IRespParser<E> getExtraParser();


    @Override
    public void execute() {
        if (mHttpCall != null && mHttpCall.isExecuted()) {
            return;
        }
        try {
            Request request = getRequest();
            mHttpCall = HttpClientManager.getInstance().newCall(request);
            mHttpCall.enqueue(new OkHttpCallback());
        } catch (Exception e) {
            LogUtils.e(e);
            handleFail(null, null, null, e);
        }
    }

    @Override
    public void cancel() {
        if (mHttpCall != null && !mHttpCall.isCanceled()) {
            mHttpCall.cancel();
        }
        taskOver();
    }

    @Override
    public void setGroupTag(String group) {
        mGroupTag = group;
    }

    @Override
    public String getGroupTag() {
        return mGroupTag;
    }

    @Override
    public String getTaskId() {
        return mTaskId;
    }

    /**
     * 设置请求组装器
     *
     * @param decorator
     */
    public void setRequestDecorator(IRequestDecorator decorator) {
        this.mRequestDecorator = decorator;
    }

    /**
     * 获取请求组装器
     *
     * @return
     */
    public IRequestDecorator getRequestDecorator() {
        return mRequestDecorator;
    }

    /**
     * 获取请求方式
     *
     * @return
     */
    public Method getRequestMethod() {
        return Method.POST;
    }

    /**
     * 获取context
     *
     * @return
     */
    public Context getContext() {
        return mContext;
    }

    //------------------------------------------------------------------------------------------------------------------

    public final String getStringByResId(@StringRes int resId) {
        return mContext.getString(resId);
    }

    public final String getStringByResId(@StringRes int resId, Object... formatArgs) {
        return mContext.getString(resId, formatArgs);
    }

    /**
     * 请求失败
     *
     * @param extra
     * @param errCode
     * @param errMsg
     */
    public void onFail(E extra, String errCode, String errMsg) {
        if (mCallback != null) {
            mCallback.onFail(extra, errCode, errMsg);
        }
    }


    /**
     * 请求成功
     *
     * @param resp
     */
    public void onSuccess(T resp, E extra, String msg) {
        if (mCallback != null) {
            mCallback.onSuccess(resp, extra, msg);
        }
    }

    //------------------------------------------------------------------------------------------------------------------

    @RequiresApi(api = Build.VERSION_CODES.N)
    private Map<String, Object> sortByKey(Map<String, Object> map) {
        Map<String, Object> result = new LinkedHashMap<>(map.size());
        map.entrySet().stream()
                .sorted(Map.Entry.comparingByKey())
                .forEachOrdered(e -> result.put(e.getKey(), e.getValue()));
        return result;
    }


    private Request getRequest() throws Exception {
        IRequestDecorator decorator = getRequestDecorator();
        Request request;
        switch (getRequestMethod()) {
            case POST:
                request = createFormRequest(decorator);
                break;
            case POST_FILE:
                request = createMultiPartRequest(decorator);
                break;
            default:
                request = createRequest(decorator);
                break;
        }
        LogUtils.i(TAG, String.format("TaskId: %s\n%s\n%s", mTaskId, request, decorator));
        return request;
    }

    private Request createMultiPartRequest(IRequestDecorator decorator) throws Exception {
        MultipartBody.Builder bodyBuilder = new MultipartBody.Builder().setType(MultipartBody.FORM);
        bodyBuilder.setType(MultipartBody.FORM);
        for (Map.Entry<String, Object> entry : decorator.getParams().entrySet()) {
            if (entry.getValue() == null) {
                continue;
            }
            if (entry.getValue() instanceof File) {
                File file = (File) entry.getValue();
                bodyBuilder.addFormDataPart(entry.getKey(),
                        file.getName(),
                        RequestBody.create(FileMediaTypeParser.parse(file), file));
            } else {
                String value = entry.getValue().toString();
                bodyBuilder.addFormDataPart(entry.getKey(), value);
            }
        }
        HttpUrl httpUrl = HttpUrl.parse(decorator.getRequestUrl());
        if (httpUrl == null) {
            throw new IllegalArgumentException("http url parse fail");
        }
        return new Request.Builder().url(httpUrl)
                .post(bodyBuilder.build())
                .tag(getGroupTag())
                .headers(Headers.of(decorator.getHeaders())).build();
    }

    private Request createFormRequest(IRequestDecorator decorator) throws Exception {
        FormBody.Builder bodyBuilder = new FormBody.Builder();
        for (Map.Entry<String, Object> entry : decorator.getParams().entrySet()) {
            if (entry.getValue() == null) {
                continue;
            }
            String value = entry.getValue().toString();
            bodyBuilder.add(entry.getKey(), value);
        }
        HttpUrl httpUrl = HttpUrl.parse(decorator.getRequestUrl());
        return new Request.Builder().url(httpUrl)
                .post(bodyBuilder.build())
                .tag(getGroupTag())
                .headers(Headers.of(decorator.getHeaders())).build();
    }

    private Request createRequest(IRequestDecorator decorator) throws Exception {
        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, Object> entry : decorator.getParams().entrySet()) {
            if (entry.getValue() == null) {
                continue;
            }
            String value = entry.getValue().toString();
            if (sb.length() > 0) {
                sb.append(AMPERSAND);
            }
            sb.append(entry.getKey()).append(EQUAL).append(value);
        }
        sb.insert(0, decorator.getRequestUrl() + QUESTION);
        HttpUrl httpUrl = HttpUrl.parse(sb.toString());
        if (httpUrl == null) {
            throw new IllegalArgumentException("http url parse fail");
        }
        return new Request.Builder().url(httpUrl)
                .headers(Headers.of(decorator.getHeaders()))
                .tag(getGroupTag())
                .build();
    }

    private void handleFail(String extra, String errCode, String errMsg, Exception e) {
        LogUtils.i(TAG, e, String.format("TaskId: %s\n%s,%s", mTaskId, errCode, errMsg));
        final Pair<String, String> error;
        if (e != null) {
            error = getErrorParser().parse(getContext(),
                    ERROR_CODE_EXCEPTION,
                    e.getMessage());
        } else {
            error = getErrorParser().parse(getContext(),
                    errCode == null ? "-1" : errCode,
                    errMsg == null ? getContext().getString(R.string.http_engine_request_fail_unknown_error) : errMsg);
        }
        E extraResult = null;
        try {
            extraResult = getExtraParser().parse(getContext(), extra, getExtraType());
        } catch (Exception e1) {
            e1.printStackTrace();
        }
        final E extraModel = extraResult;
        if (!CATCH_ERROR_CODE.equals(error.first)) {
            UiHandler.postOnUiThread(new Runnable() {
                @Override
                public void run() {
                    taskOver();
                    onFail(extraModel, error.first, error.second);
                }
            });
        }
    }

    private void handleSuccess(String resp, String extra, final String msg) {
        T respResult = null;
        E extraResult=null;
        try {
            if(resp!=null && resp!=""){
                respResult = getRespParser().parse(getContext(), resp, getRespType());
                if(respResult==null){
                    respResult = (T) resp;
                }
            }else{
//               respResult = (T) resp;
            }
              extraResult = getExtraParser().parse(getContext(), extra, getExtraType());
            final T finalRespResult = respResult;
            final E finalExtraResult = extraResult;
            UiHandler.postOnUiThread(new Runnable() {
                @Override
                public void run() {
                    taskOver();
                    onSuccess(finalRespResult, finalExtraResult, msg);
                }
            });
        } catch (Exception e) {
            LogUtils.e(e);
            handleFail(null, null, getContext().getString(R.string.http_engine_request_fail_resp_parse_error), null);
        }
    }

    private void taskOver() {
        mHttpCall = null;
    }

    //------------------------------------------------------------------------------------------------------------------

    private class OkHttpCallback implements Callback {

        @Override
        public void onFailure(Call call, IOException e) {
            handleFail(null, null, null, e);
        }

        @Override
        public void onResponse(Call call, Response response) throws IOException {
            LogUtils.i(TAG, String.format("TaskId: %s\n%s", mTaskId, response));
            if (response.isSuccessful()) {
                try {
                    String respStr = response.body().string();
                    Log.e(TAG, "onResponse: "+respStr );
                    LogUtils.i(TAG, String.format("TaskId: %s\n%s", mTaskId, respStr));
                    IResult result = getRespProcessor().process(respStr);
                    if (result.isSuccess()) {
                        handleSuccess(result.getResp(), result.getExtra(), result.getMsg());
                    } else {
                        handleFail(result.getExtra(), result.getErrCode(), result.getMsg(), null);
                    }
                } catch (Exception e) {
                    LogUtils.e(e);
                    handleFail(null, null, null, e);
                }
            } else {
                handleFail(null, "-1", getStringByResId(R.string.http_engine_request_fail, String.valueOf(response.code())), null);
            }
        }
    }
}
